﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace TetraEngine
{
    /// <summary>
    /// The logical implementation of a game of TetraZune.
    /// </summary>
    public class TetraGame
    {
        protected readonly int LineClearedScore = 50;
        protected readonly int PieceDropScore = 1;


        private GameBoard gameBoard = new GameBoard();

        private Piece currentPiece;
        private Piece nextPiece;
        private Point pieceLoc;

        private int level = 0;
        private int lines = 0;
        private int singles = 0;
        private int doubles = 0;
        private int triples = 0;
        private int tetras = 0;
        private int specials = 0;
        private int score = 0;

        private bool isRowClear = false;
        private int[] clearRows = new int[0];
        private bool lastPieceSet = false;

        private int tetraBonus = 0;
        private bool levelUp = false;

        private bool gameOver = false;

        public TetraGame()
        {
            Reset();
        }

        public void Reset()
        {
            level = 0;
            lines = 0;
            singles = 0;
            doubles = 0;
            triples = 0;
            tetras = 0;
            specials = 0;
            score = 0;

            isRowClear = false;
            clearRows = new int[0];
            lastPieceSet = false;

            tetraBonus = 0;
            levelUp = false;
            gameOver = false;
            UpdatePieces();
            UpdatePieces();
        }

        public GameBoard GameBoard { get { return gameBoard; } }

        /// <summary>
        /// The piece that is currently falling
        /// </summary>
        public Piece Piece { get { return currentPiece; } }

        public Point PieceLocation { get { return (currentPiece == null ? Point.Zero : pieceLoc); } }

        /// <summary>
        /// The piece that will fall next
        /// </summary>
        public Piece NextPiece { get { return nextPiece; } }

        /// <summary>
        /// The score of the game so far
        /// </summary>
        public int Score { get { return score; } }

        /// <summary>
        /// The current level
        /// </summary>
        public int Level { get { return level; } }

        /// <summary>
        /// Returns true if tetras are currently earning double Vector2s
        /// </summary>
        public bool DoubleTetras { get { return tetraBonus > 0; } }

        /// <summary>
        /// Returns true if the user is receiving a Special bonus for each tetra
        /// </summary>
        public bool TetraSpecial { get { return tetraBonus >= 4; } }

        /// <summary>
        /// Returns true if the user has lost the game.
        /// </summary>
        public bool GameOver { get { return gameOver; } }

        public int Singles { get { return singles; } }
        public int Doubles { get { return doubles; } }
        public int Triples { get { return triples; } }
        public int Tetras { get { return tetras; } }
        public int Specials { get { return specials; } }

        /// <summary>
        /// The indices of the rows the user just cleared.  Returns a zero-length 
        /// array if no rows were cleared last step
        /// </summary>
        public int[] ClearRows
        {
            get { return clearRows; }
        }

        /// <summary>
        /// True if the last Step() placed a piece on the board.
        /// </summary>
        public bool PieceJustSet { get { return lastPieceSet; } }

        /// <summary>
        /// Determines if a piece can move downward without overlapping another 
        /// piece or leaving the game board
        /// </summary>
        /// <returns>True if moving down is allowed, false otherwise</returns>
        public bool CanPieceMoveDown()
        {
            if (currentPiece == null) return false;
            return !gameBoard.CollidesWithPiece(currentPiece, 
                new Point(pieceLoc.X, pieceLoc.Y + 1));
        }

        /// <summary>
        /// Drops the current piece down.  Usually should be
        /// called when the user presses 'Down'.
        /// </summary>
        public void DropPiece()
        {
            if (CanPieceMoveDown())
            {
                pieceLoc.Y++;
                score += PieceDropScore;
            }
        }

        public void MoveLeft()
        {
            if (currentPiece == null) return;
            pieceLoc.X--;
            if (gameBoard.CollidesWithPiece(currentPiece, pieceLoc))
                pieceLoc.X++;
        }
        public void MoveRight()
        {
            if (currentPiece == null) return;
            pieceLoc.X++;
            if (gameBoard.CollidesWithPiece(currentPiece, pieceLoc))
                pieceLoc.X--;
        }

        /// <summary>
        /// Attempts to rotate the current piece 90
        /// degrees Counter-Clockwise.
        /// </summary>
        public void RotatePieceCCW()
        {
            if (currentPiece == null) return;
            currentPiece.RotationIndex--;
            if (gameBoard.CollidesWithPiece(currentPiece, pieceLoc))
                currentPiece.RotationIndex++;
        }
        /// <summary>
        /// Attempts to rotate the current piece 90
        /// degrees Clockwise.
        /// </summary>
        public void RotatePieceCW()
        {
            if (currentPiece == null) return;
            currentPiece.RotationIndex++;
            if (gameBoard.CollidesWithPiece(currentPiece, pieceLoc))
                currentPiece.RotationIndex--;
        }

        /// <summary>
        /// Steps the game logic ahead one unit.
        /// </summary>
        public void Step()
        {
            if (gameOver) return;

            levelUp = false;
            if (lastPieceSet)
            {
                UpdateRows();
                UpdatePieces();
                lastPieceSet = false;
                return;
            }
            
            if (!CanPieceMoveDown())
            {
                gameBoard.PlacePiece(currentPiece, pieceLoc);
                clearRows = GetClearRows();
                if (clearRows.Length > 0) isRowClear = true;
                currentPiece = null;
                lastPieceSet = true;
                return;
            }

            pieceLoc.Y++;

        }

        private void UpdateRows()
        {
            for (int i = 0; i < clearRows.Length; i++)
                ClearRow(clearRows[i]);
            if (clearRows.Length == 4)
            {
                if (tetraBonus > 0)  //double points on a tetra spree!
                    score += clearRows.Length * LineClearedScore;
                tetraBonus++;
            }
            else if (clearRows.Length > 0)
                tetraBonus = 0;
            if (clearRows.Length == 1) singles++;
            else if (clearRows.Length == 2) doubles++;
            else if (clearRows.Length == 3) triples++;
            else if (clearRows.Length == 4)
            {
                tetras++;
                if (tetraBonus > 4) specials++;
            }

            score += clearRows.Length * LineClearedScore;
            lines += clearRows.Length;
            if (lines / 10 > level)
            {
                level++;
                levelUp = true;
            }
            isRowClear = false;
            clearRows = new int[0];
        }

        protected void UpdatePieces()
        {
            currentPiece = nextPiece;
            nextPiece = GeneratePiece();
            pieceLoc = new Point(3, 0);
            if (gameBoard.CollidesWithPiece(currentPiece, pieceLoc))
            {
                gameOver = true;
                currentPiece = null;
            }
        }

        /// <summary>
        /// Removes the given row from the board, and moves all rows
        /// above it down by one.
        /// </summary>
        /// <param name="rowIndex"></param>
        private void ClearRow(int rowIndex)
        {
            for (int i = rowIndex; i > 0; i--)
                for (int j = 0; j < GameBoard.GameBoardColumns; j++)
                    gameBoard[i, j] = gameBoard[i - 1, j];
        }

        /// <summary>
        /// Returns an array of length 0 to 4 where each element
        /// holds the row index of a full (clearable) row.
        /// </summary>
        /// <returns>An array of integers holding row indices for
        /// clearable rows, going from smallest to largest index.</returns>
        private int[] GetClearRows()
        {
            int[] rows = new int[4];
            int rowsIx = 0;
            bool rowHasHole;
            for (int i = 0; i < GameBoard.GameBoardRows; i++)
            {
                rowHasHole = false;
                for (int j = 0; j < GameBoard.GameBoardColumns; j++)
                    if (gameBoard[i, j] == 0)
                    {
                        rowHasHole = true;
                        break;
                    }
                if (!rowHasHole) rows[rowsIx++] = i;
            }
            int[] ret = new int[rowsIx];
            for (int i = 0; i < rowsIx; i++)
                ret[i] = rows[i];
            return ret;
        }

        private Random r = new Random();
        private int[] bag = new int[8];
        private int index = 8;

        protected virtual Piece GeneratePiece()
        {
            if (index >= bag.Length)
                RefillBag();
            switch (bag[index++])
            {
                case 0:
                    return new SquarePiece();
                case 1:
                    return new LinePiece();
                case 2:
                    return new TPiece();
                case 3:
                    return new LPiece();
                case 4:
                    return new JPiece();
                case 5:
                    return new ZPiece();
                case 6:
                    return new SPiece();
            }
            throw new ApplicationException();
        }

        private void RefillBag()
        {
            int next;
            for (index = 0; index < 7; )
            {
                next = r.Next(7);
                bag[index] = 0;
                if (Array.IndexOf<int>(bag, next, 0, index) < 0)
                    bag[index++] = next;
            }
            bag[index] = r.Next(7);
            index = 0;
        }
    }
}
